package com.company.cloud.club.modules.core.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.lang.ref.SoftReference;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 日期转换运算工具
 *
 * @author precision
 */
public final class DateUtils {

    private static Set<String> formatSet = new HashSet<>();

    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String YYYYMMDD = "yyyyMMdd";
    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
    public static final String YYYY = "yyyy";
    public static final String FORMAT_YYYYMM = "yyyyMM";
    public static final String FORMAT_HH_MM_SS = "HH:mm:ss";
    public static final String FORMAT_H_M = "H:m";
    public static final String FORMAT_HH_M = "HH:m";
    public static final String FORMAT_H_MM = "H:mm";
    public static final String FORMAT_HH_MM = "HH:mm";

    public static final DateTimeFormatter YYYYMMDD_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYYMMDD);
    public static final DateTimeFormatter YYYY_MM_DD_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY_MM_DD);
    public static final DateTimeFormatter YYYY_MM_DD_HH_MM_SS_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
    public static final DateTimeFormatter YYYYMMDDHHMMSS_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYYMMDDHHMMSS);
    public static final DateTimeFormatter YYYY_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY);

    public static final Map<String, DateTimeFormatter> TIME_FORMATTER_MAP = new HashMap<>();

    public static final Map<String, DateTimeFormatter> DATE_TIME_FORMATTER_MAP = new HashMap<>();

    public static final DateTimeFormatter H_M_FORMATTER = DateTimeFormatter.ofPattern(FORMAT_H_M);
    public static final DateTimeFormatter HH_M_FORMATTER = DateTimeFormatter.ofPattern(FORMAT_HH_M);
    public static final DateTimeFormatter H_MM_FORMATTER = DateTimeFormatter.ofPattern(FORMAT_H_MM);
    public static final DateTimeFormatter HH_MM_FORMATTER = DateTimeFormatter.ofPattern(FORMAT_HH_MM);

    public static final String YYYY_M_D = "yyyy-M-d";
    public static final String YYYY_M_DD = "yyyy-M-dd";
    public static final String YYYY_MM_D = "yyyy-MM-d";

    public static final DateTimeFormatter YYYY_M_D_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY_M_D);
    public static final DateTimeFormatter YYYY_M_DD_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY_M_DD);
    public static final DateTimeFormatter YYYY_MM_D_DATE_FORMATTER = DateTimeFormatter.ofPattern(YYYY_MM_D);

    static {
        formatSet.add(YYYYMMDDHHMMSS);
        formatSet.add(YYYYMMDD);
        formatSet.add(YYYY_MM_DD);
        formatSet.add(YYYY);
        formatSet.add(YYYY_MM_DD_HH_MM_SS);
        formatSet.add(FORMAT_YYYYMM);
        formatSet.add(FORMAT_HH_MM_SS);

        TIME_FORMATTER_MAP.put("11", H_M_FORMATTER);
        TIME_FORMATTER_MAP.put("21", HH_M_FORMATTER);
        TIME_FORMATTER_MAP.put("12", H_MM_FORMATTER);
        TIME_FORMATTER_MAP.put("22", HH_MM_FORMATTER);

        DATE_TIME_FORMATTER_MAP.put(YYYYMMDD, YYYYMMDD_DATE_FORMATTER);
        DATE_TIME_FORMATTER_MAP.put(YYYY_MM_DD, YYYY_MM_DD_DATE_FORMATTER);
        DATE_TIME_FORMATTER_MAP.put(YYYY_MM_DD_HH_MM_SS, YYYY_MM_DD_HH_MM_SS_DATE_FORMATTER);
        DATE_TIME_FORMATTER_MAP.put(YYYYMMDDHHMMSS, YYYYMMDDHHMMSS_DATE_FORMATTER);
        DATE_TIME_FORMATTER_MAP.put(YYYY, YYYY_DATE_FORMATTER);
    }

    public static LocalTime parseLocalTime(String timeStr) {
        String trimedTimeStr = timeStr.trim();
        String[] array = trimedTimeStr.split(":");
        if (array.length != 2 || array[0] == null || array[1] == null) {
            return null;
        }
        String key = array[0].length() + "" + array[1].length();
        DateTimeFormatter dateTimeFormatter = TIME_FORMATTER_MAP.get(key);
        if (dateTimeFormatter == null) {
            return null;
        }
        return LocalTime.parse(trimedTimeStr, dateTimeFormatter);
    }

    public static LocalDate parseDateStrLocalDate(String timeStr) {
        String trimedTimeStr = timeStr.replaceAll(" ", "");
        if (StringUtils.isBlank(trimedTimeStr)) {
            return LocalDate.now();
        }
        String[] timeArray = trimedTimeStr.split("-");
        if (timeArray.length != 3) {
            return LocalDate.now();
        }
        StringBuilder timeFormat = new StringBuilder();
        timeFormat.append(timeArray[0]).append("-");
        final String month = timeArray[1];
        final String day = timeArray[2];
        final int mLen = month.length();
        final  int dLen = day.length();
        if ((mLen != 1 && mLen != 2) || (dLen != 1 && dLen != 2)) {
            return LocalDate.now();
        }
        timeFormat.append(mLen == 1 ? "0" + month : month);
        timeFormat.append("-");
        timeFormat.append(dLen == 1 ? "0" + day : day);
        return LocalDate.parse(timeFormat.toString(), YYYY_MM_DD_DATE_FORMATTER);
    }

    static final class DateFormatHolder {
        private static final ThreadLocal<SoftReference<Map<String, SimpleDateFormat>>> THREADLOCAL_FORMATS = new ThreadLocal<>();

        DateFormatHolder() {}

        static SimpleDateFormat formatFor(String pattern) {
            SoftReference<Map<String, SimpleDateFormat>> ref = THREADLOCAL_FORMATS.get();
            Map<String, SimpleDateFormat> formats = ref == null ? null : ref.get();
            if (formats == null) {
                formats = new HashMap<>();
                THREADLOCAL_FORMATS.set(new SoftReference<>(formats));
            }
            SimpleDateFormat format =  formats.get(pattern);
            if (format == null) {
                format = new SimpleDateFormat(pattern);
                format.setTimeZone(TimeZone.getTimeZone("GMT+8"));
                formats.put(pattern, format);
            }
            return format;
        }

        public static void clearThreadLocal() {
            THREADLOCAL_FORMATS.remove();
        }
    }

    public static String formatDate(Date date, String pattern) {
        Assert.notNull(date, "Date");
        Assert.notNull(pattern, "Pattern");
        SimpleDateFormat formatFor = DateFormatHolder.formatFor(pattern);
        return formatFor.format(date);
    }

    public static String format(Date date) {
        return formatDate(date, YYYY_MM_DD_HH_MM_SS);
    }

    public static Date parseDate(String s, String format) throws ParseException {
        if (formatSet.contains(format)) {
            SimpleDateFormat dateFormat = new SimpleDateFormat(format);
            return dateFormat.parse(s);
        }
        throw new ParseException("DateTime.parseDate(String,String):error" + format, 0);
    }

    /**
     * 剩余分钟数
     *
     * @param date 结束点日期
     * @return 分钟数
     */
    public static final long minutesRemaining(Date date) {
        return (date.getTime() - System.currentTimeMillis()) / 1000 / 60;
    }

    /**
     * 剩余小时
     *
     * @param date 结束点日期
     * @return 小时数
     */
    public static final long remainingHours(Date date) {
        return minutesRemaining(date) / 60;
    }

    /**
     * 剩余天数
     *
     * @param date 结束点日期
     * @return 天数
     */
    public static final long remainingDays(Date date) {
        return remainingHours(date) / 24;
    }

    public static LocalDateTime date2LocalDateTime(Date date) {
        if (date == null) {
            throw new NullPointerException("param date NULL.");
        }
        Instant instant = date.toInstant();
        return instant.atZone(Constants.DEFAULT_ZONE_ID).toLocalDateTime();
    }

    public static LocalDateTime long2LocalDateTime(Long timestamp) {
        if (timestamp == null) {
            throw new NullPointerException("timestamp is NULL.");
        }
        Instant instant = Instant.ofEpochMilli(timestamp);
        return instant.atZone(Constants.DEFAULT_ZONE_ID).toLocalDateTime();
    }

    public static Long localDateTime2Long(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            throw new NullPointerException("localDateTime is NULL.");
        }
        return localDateTime.atZone(Constants.DEFAULT_ZONE_ID).toInstant().toEpochMilli();
    }

    public static String formatLocalDateTime(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            throw new NullPointerException("localDateTime is NULL.");
        }
        return localDateTime.format(YYYYMMDD_DATE_FORMATTER);
    }

    public static String formatLocalDate(LocalDate localDate) {
        if (localDate == null) {
            throw new NullPointerException("localDateTime is NULL.");
        }
        return localDate.format(YYYYMMDD_DATE_FORMATTER);
    }

    public static String formatLocalDateTime(LocalDateTime localDateTime, String pattern) {
        if (!DATE_TIME_FORMATTER_MAP.containsKey(pattern)) {
            throw new DateFormatException("日期格式非法.");
        }
        if (localDateTime == null) {
            throw new NullPointerException("localDateTime is NULL.");
        }
        return localDateTime.format(DATE_TIME_FORMATTER_MAP.get(pattern));
    }

    public static String formatLocalDate(LocalDate localDate, String pattern) {
        if (!DATE_TIME_FORMATTER_MAP.containsKey(pattern)) {
            throw new DateFormatException("日期格式非法.");
        }
        if (localDate == null) {
            throw new NullPointerException("localDateTime is NULL.");
        }
        return localDate.format(DATE_TIME_FORMATTER_MAP.get(pattern));
    }

    public static int dayOfWeek(LocalDateTime localDateTime) {
        return localDateTime.getDayOfWeek().getValue();
    }

    public static class DateFormatException extends RuntimeException {

        public DateFormatException(String message) {
            super(message);
        }

        public DateFormatException(String message, Throwable cause) {
            super(message, cause);
        }
    }

}
